package userInterface.Panels;

import dataAccess.*;
import logging.*;
import settings.*;

import java.awt.datatransfer.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;

/**
 * UI JPanel for editing Environment objects.
 * @author BDS
 */
public class PanelEnvironments extends PanelBase
{
	private static final long serialVersionUID = 1L;

	private Environment currentEnvironment;
	private Environment copiedEnvironment;
	private int copyNumber;
	
	private ActionListener environmentActionListener;
	private ListSelectionListener environmentsListSelectionListener;
	private TransferHandler environmentTransferHandler;
	private FocusAdapter environmentFocusAdapter;
	private KeyAdapter environmentKeyAdapter;
	
	private SpringLayout slEnvironmentLayout;
	
	private JButton btnAdd;
	private JButton btnRemove;
	private JButton btnCopy;
	private JButton btnPaste;
	private DefaultListModel<Environment> environments;
	private JList<Environment> lstEnvironments;
	private JScrollPane scpEnvironments;
	
	private JCheckBox chkEnabled;
	private JLabel lblName;
	private JLabel lblPath;
	private JLabel lblArgument1;
	private JLabel lblArgument2;
	private JFormattedTextField txtName;
	private JFormattedTextField txtPath;
	private JButton btnPathBrowse;
	private JFormattedTextField txtArgument1;
	private JFormattedTextField txtArgument2;
	
	private JButton btnSave;
	private JButton btnRevert;
	
	/**
	 * Class constructor.
	 */
	public PanelEnvironments()
	{
		environmentActionListener = new EnvironmentActionListener();
		environmentsListSelectionListener = new EnvironmentListSelectionListener();
		environmentTransferHandler = new EnvironmentTransferHandler();
		environmentFocusAdapter = new EnvironmentFocusAdapter();
		environmentKeyAdapter = new EnvironmentKeyAdapter();

		slEnvironmentLayout = new SpringLayout();
		setLayout(slEnvironmentLayout);
		
		btnAdd = new JButton();
		btnAdd.setIcon(new ImageIcon(getClass().getResource("/images/add.png")));
		btnAdd.setToolTipText("Add a new environment");
		btnAdd.setMnemonic(KeyEvent.VK_N);
		btnAdd.setActionCommand("Add");
		btnAdd.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, btnAdd, 10, SpringLayout.WEST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, btnAdd, 10, SpringLayout.NORTH, this);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnAdd, 50, SpringLayout.WEST, btnAdd);
		add(btnAdd);

		btnRemove = new JButton();
		btnRemove.setIcon(new ImageIcon(getClass().getResource("/images/remove.png")));
		btnRemove.setToolTipText("Remove the selected environment");
		btnRemove.setEnabled(false);
		btnRemove.setMnemonic(KeyEvent.VK_E);
		btnRemove.setActionCommand("Remove");
		btnRemove.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, btnRemove, 30, SpringLayout.EAST, btnAdd);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, btnRemove, 10, SpringLayout.NORTH, this);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnRemove, 50, SpringLayout.WEST, btnRemove);
		add(btnRemove);
		
		btnCopy = new JButton();
		btnCopy.setIcon(new ImageIcon(getClass().getResource("/images/copy.png")));
		btnCopy.setToolTipText("Copy the selected environment");
		btnCopy.setEnabled(false);
		btnCopy.setMnemonic(KeyEvent.VK_C);
		btnCopy.setActionCommand("Copy");
		btnCopy.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, btnCopy, 30, SpringLayout.EAST, btnRemove);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, btnCopy, 10, SpringLayout.NORTH, this);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnCopy, 50, SpringLayout.WEST, btnCopy);
		add(btnCopy);
		
		btnPaste = new JButton();
		btnPaste.setIcon(new ImageIcon(getClass().getResource("/images/paste.png")));
		btnPaste.setToolTipText("Paste the copied environment");
		btnPaste.setEnabled(false);
		btnPaste.setMnemonic(KeyEvent.VK_P);
		btnPaste.setActionCommand("Paste");
		btnPaste.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, btnPaste, 30, SpringLayout.EAST, btnCopy);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, btnPaste, 10, SpringLayout.NORTH, this);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnPaste, 50, SpringLayout.WEST, btnPaste);
		add(btnPaste);

		environments = new DefaultListModel<Environment>();
		lstEnvironments = new JList<Environment>(environments);
		lstEnvironments.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		lstEnvironments.setDragEnabled(true);
		lstEnvironments.setDropMode(DropMode.INSERT);
		lstEnvironments.addListSelectionListener(environmentsListSelectionListener);
		lstEnvironments.setTransferHandler(environmentTransferHandler);
		
		scpEnvironments = new JScrollPane(lstEnvironments);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, scpEnvironments, 10, SpringLayout.WEST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, scpEnvironments, 0, SpringLayout.SOUTH, btnAdd);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, scpEnvironments, 0, SpringLayout.EAST, btnPaste);
		slEnvironmentLayout.putConstraint(SpringLayout.SOUTH, scpEnvironments, -10, SpringLayout.SOUTH, this);
		add(scpEnvironments);
		
		chkEnabled = new JCheckBox("Enabled");
		chkEnabled.setEnabled(false);
		chkEnabled.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, chkEnabled, 6, SpringLayout.EAST, scpEnvironments);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, chkEnabled, 0, SpringLayout.NORTH, scpEnvironments);
		add(chkEnabled);

		lblName = new JLabel("Name:");
		lblName.setEnabled(false);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, lblName, 6, SpringLayout.EAST, scpEnvironments);
		add(lblName);

		lblPath = new JLabel("Path:");
		lblPath.setEnabled(false);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, lblPath, 6, SpringLayout.EAST, scpEnvironments);
		add(lblPath);

		lblArgument1 = new JLabel("Argument 1:");
		lblArgument1.setEnabled(false);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, lblArgument1, 6, SpringLayout.EAST, scpEnvironments);
		add(lblArgument1);

		lblArgument2 = new JLabel("Argument 2:");
		lblArgument2.setEnabled(false);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, lblArgument2, 6, SpringLayout.EAST, scpEnvironments);
		add(lblArgument2);

		txtName = new JFormattedTextField();
		txtName.setEnabled(false);
		txtName.addFocusListener(environmentFocusAdapter);
		txtName.addKeyListener(environmentKeyAdapter);
		slEnvironmentLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblName, 0, SpringLayout.VERTICAL_CENTER, txtName);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, txtName, -300, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, txtName, 6, SpringLayout.SOUTH, chkEnabled);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, txtName, -10, SpringLayout.EAST, this);
		add(txtName);

		txtPath = new JFormattedTextField();
		txtPath.setEnabled(false);
		txtPath.addFocusListener(environmentFocusAdapter);
		txtPath.addKeyListener(environmentKeyAdapter);
		slEnvironmentLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblPath, 0, SpringLayout.VERTICAL_CENTER, txtPath);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, txtPath, -300, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, txtPath, 6, SpringLayout.SOUTH, txtName);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, txtPath, -50, SpringLayout.EAST, this);
		add(txtPath);
		
		btnPathBrowse = new JButton();
		btnPathBrowse.setIcon(new ImageIcon(getClass().getResource("/images/browse_tiny.png")));
		btnPathBrowse.setToolTipText("Browse...");
		btnPathBrowse.setEnabled(false);
		btnPathBrowse.setActionCommand("Browse");
		btnPathBrowse.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, btnPathBrowse, 6, SpringLayout.EAST, txtPath);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, btnPathBrowse, 0, SpringLayout.NORTH, txtPath);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnPathBrowse, -10, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.SOUTH, btnPathBrowse, 0, SpringLayout.SOUTH, txtPath);
		add(btnPathBrowse);

		txtArgument1 = new JFormattedTextField();
		txtArgument1.setEnabled(false);
		txtArgument1.addFocusListener(environmentFocusAdapter);
		txtArgument1.addKeyListener(environmentKeyAdapter);
		slEnvironmentLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblArgument1, 0, SpringLayout.VERTICAL_CENTER, txtArgument1);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, txtArgument1, -300, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, txtArgument1, 6, SpringLayout.SOUTH, txtPath);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, txtArgument1, -10, SpringLayout.EAST, this);
		add(txtArgument1);

		txtArgument2 = new JFormattedTextField();
		txtArgument2.setEnabled(false);
		txtArgument2.addFocusListener(environmentFocusAdapter);
		txtArgument2.addKeyListener(environmentKeyAdapter);
		slEnvironmentLayout.putConstraint(SpringLayout.VERTICAL_CENTER, lblArgument2, 0, SpringLayout.VERTICAL_CENTER, txtArgument2);
		slEnvironmentLayout.putConstraint(SpringLayout.WEST, txtArgument2, -300, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.NORTH, txtArgument2, 6, SpringLayout.SOUTH, txtArgument1);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, txtArgument2, -10, SpringLayout.EAST, this);
		add(txtArgument2);

		btnSave = new JButton("Save");
		btnSave.setMnemonic(KeyEvent.VK_S);
		btnSave.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.SOUTH, btnSave, -10, SpringLayout.SOUTH, this);
		add(btnSave);

		btnRevert = new JButton("Revert");
		btnRevert.setMnemonic(KeyEvent.VK_R);
		btnRevert.addActionListener(environmentActionListener);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnSave, -6, SpringLayout.WEST, btnRevert);
		slEnvironmentLayout.putConstraint(SpringLayout.EAST, btnRevert, -10, SpringLayout.EAST, this);
		slEnvironmentLayout.putConstraint(SpringLayout.SOUTH, btnRevert, -10, SpringLayout.SOUTH, this);
		add(btnRevert);
		
		setEditedState(false);
	}

	/**
	 * Prompts the user to save any relevant changes, if necessary.
	 */
	@Override
	public void promptSaveChanges()
	{
		if (btnSave.isEnabled())
		{
			if (JOptionPane.showConfirmDialog(null,
											  "Save all environment changes?",
											  "Environments",
											  JOptionPane.YES_NO_OPTION,
											  JOptionPane.WARNING_MESSAGE) == JOptionPane.YES_OPTION)
			{
				save();
			}
		}
		
		setEditedState(false);
	}
	
	/**
	 * Updates the availability of all Environment-related controls.
	 */
	@Override
	public void setEnabled(boolean enabled)
	{
		btnCopy.setEnabled(enabled);
		btnRemove.setEnabled(enabled);
		
		lblName.setEnabled(enabled);
		lblPath.setEnabled(enabled);
		lblArgument1.setEnabled(enabled);
		lblArgument2.setEnabled(enabled);
	
		txtName.setEnabled(enabled);
		txtPath.setEnabled(enabled);
		btnPathBrowse.setEnabled(enabled);
		txtArgument1.setEnabled(enabled);
		txtArgument2.setEnabled(enabled);
		chkEnabled.setEnabled(enabled);
	}
	
	/**
	 * Saves all Environment objects.
	 */
	public void save()
	{
		setEditedState(false);
		
		Settings settings = LocalDataAccess.loadSettings();

		// DefaultListModel does not support toArray(T[]).
		Environment[] newEnvironments = new Environment[environments.getSize()];
		environments.copyInto(newEnvironments);
		settings.setEnvironments(new ArrayList<Environment>(Arrays.asList(newEnvironments)));

		LocalDataAccess.saveSettings(settings);
		resetFocus();
	}
	
	/**
	 * Loads all saved Environment objects.
	 */
	@Override
	public void load()
	{
		setEditedState(false);
		
		environments.clear();
		for (Environment environment : LocalDataAccess.loadSettings().getEnvironments())
		{
			environments.addElement(environment);
		}
		lstEnvironments.setSelectedIndex(-1);
		
		currentEnvironment = null;
		updateEnvironment();
		
		copiedEnvironment = null;
		btnPaste.setEnabled(false);
		btnPaste.setToolTipText("Paste the copied environment");
		
		resetFocus();
	}

	/**
	 * Sets the availability of the Save and Revert buttons based on the given state.
	 * @param edited True if any Environment objects have been changed; false if they reflect the settings.txt file.
	 */
	private void setEditedState(boolean edited)
	{
		btnSave.setEnabled(edited);
		btnRevert.setEnabled(edited);
	}
	
	/**
	 * Updates the selected Environment to reflect what the UI currently displays.
	 * If no Environment is selected, the UI will be updated.
	 */
	private void updateEnvironment()
	{			
		if (currentEnvironment == null)
		{
			txtName.setText("");
			txtPath.setText("");
			txtArgument1.setText("");
			txtArgument2.setText("");
			chkEnabled.setSelected(false);
			
			// Ensure that a recently disabled element is not focused.
			resetFocus();
		}
		else if (environments.contains(currentEnvironment))
		{
			if (!currentEnvironment.getName().equals(txtName.getText()) || 
				!currentEnvironment.getPath().equals(txtPath.getText()) ||
				!currentEnvironment.getArgument1().equals(new String(txtArgument1.getText())) ||
				!currentEnvironment.getArgument2().equals(new String(txtArgument2.getText())) ||
				currentEnvironment.getEnabled() != chkEnabled.isSelected())
			{
				setEditedState(true);
			}
			
			// Protect against setting the Environment name to nothing.
			String name = txtName.getText();
			if (name == null || name.equals(""))
			{
				txtName.setText("New");
			}
			
			currentEnvironment.setName(txtName.getText());
			currentEnvironment.setPath(txtPath.getText());
			currentEnvironment.setArgument1(txtArgument1.getText());
			currentEnvironment.setArgument2(txtArgument2.getText());
			currentEnvironment.setEnabled(chkEnabled.isSelected());
			
			environments.set(environments.indexOf(currentEnvironment), currentEnvironment);
		}
		
		currentEnvironment = lstEnvironments.getSelectedValue();
		
		setEnabled(currentEnvironment != null);
		
		if (currentEnvironment != null)
		{
			btnRemove.setToolTipText("Remove the " + currentEnvironment.toString() + " environment");
			btnCopy.setToolTipText("Copy the " + currentEnvironment.toString() + " environment");
		}
		else
		{
			btnRemove.setToolTipText("Remove the selected environment");
			btnCopy.setToolTipText("Copy the selected environment");
		}
	}
	 
	/**
	 * ActionListener for the Environment JButton objects.
	 * @author BDS
	 */
	private class EnvironmentActionListener implements ActionListener
	{
		/**
		 * Executes specific commands based on the ActionEvent.
		 * Add - Creates a new Environment.
		 * Copy - Copies the selected Environment to the pseudo clipboard.
		 * Paste - Adds the copied Environment from the pseudo clipboard.
		 * Remove - Removes the selected Environment.
		 * Browse - Opens a JFileChooser for populating the Environment's path.
		 * Enabled - Update the selected Environment.
		 * Save - Saves all Environments.
		 * Revert - Reverts all Environment objects to their previously saved state.
		 */
		@Override
		public void actionPerformed(ActionEvent actionEvent)
		{
			if (actionEvent.getActionCommand().equals(btnAdd.getActionCommand()))
			{
				setEditedState(true);

				currentEnvironment = new Environment();

				txtName.setText(currentEnvironment.getName());
				txtPath.setText(currentEnvironment.getPath());
				txtArgument1.setText(currentEnvironment.getArgument1());
				txtArgument2.setText(currentEnvironment.getArgument2());
				chkEnabled.setSelected(currentEnvironment.getEnabled());

				environments.insertElementAt(currentEnvironment, lstEnvironments.getSelectedIndex() + 1);
				lstEnvironments.setSelectedIndex(lstEnvironments.getSelectedIndex() + 1);
				lstEnvironments.ensureIndexIsVisible(lstEnvironments.getSelectedIndex());
				
				updateEnvironment();
			}
			else if (actionEvent.getActionCommand().equals(btnRemove.getActionCommand()))
			{
				if (currentEnvironment != null)
				{
					setEditedState(true);
					
					int currentIndex = lstEnvironments.getSelectedIndex();
					environments.removeElement(currentEnvironment);
					
					currentEnvironment = null;
					updateEnvironment();
					lstEnvironments.setSelectedIndex(currentIndex);
				}
			}
			else if (actionEvent.getActionCommand().equals(btnCopy.getActionCommand()))
			{
				copiedEnvironment = new Environment(currentEnvironment);
				copyNumber = 1;
				
				btnPaste.setEnabled(true);
				btnPaste.setToolTipText("Paste the " + currentEnvironment.toString() + " environment");
			}
			else if (actionEvent.getActionCommand().equals(btnPaste.getActionCommand()))
			{
				setEditedState(true);
				
				Environment pastedEnvironment = new Environment(copiedEnvironment);
				pastedEnvironment.setName(pastedEnvironment.getName() + " - Copy " + copyNumber);
				copyNumber++;

				environments.insertElementAt(pastedEnvironment, lstEnvironments.getSelectedIndex() + 1);
				lstEnvironments.setSelectedIndex(lstEnvironments.getSelectedIndex() + 1);
				lstEnvironments.ensureIndexIsVisible(lstEnvironments.getSelectedIndex());
				
				updateEnvironment();
			}
			else if (actionEvent.getActionCommand().equals(btnPathBrowse.getActionCommand()))
			{
				JFileChooser fileChooser = new JFileChooser(System.getProperty("user.dir"));
				if (fileChooser.showDialog(((JButton) actionEvent.getSource()).getParent(), "Select") == JFileChooser.APPROVE_OPTION)
				{
					txtPath.setText(fileChooser.getSelectedFile().getPath());
				}
			}
			else if (actionEvent.getActionCommand().equals(chkEnabled.getActionCommand()))
			{
				updateEnvironment();
			}
			else if (actionEvent.getActionCommand().equals(btnSave.getActionCommand()))
			{
				save();
			}
			else if (actionEvent.getActionCommand().equals(btnRevert.getActionCommand()))
			{
				load();
			}
		}
	}

	/**
	 * ListSelectionListener for the Environment JList.
	 * @author BDS
	 */
	private class EnvironmentListSelectionListener implements ListSelectionListener
	{
		/**
		 * Updates the UI with the selected Environment.
		 */
		@Override
		public void valueChanged(ListSelectionEvent listSelectionEvent)
		{
			updateEnvironment();
			
			if (currentEnvironment != null)
			{
				txtName.setText(currentEnvironment.getName());
				txtPath.setText(currentEnvironment.getPath());
				txtArgument1.setText(currentEnvironment.getArgument1());
				txtArgument2.setText(currentEnvironment.getArgument2());
				chkEnabled.setSelected(currentEnvironment.getEnabled());
			}
		}
	}
	
	/**
	 * TransferHandler for the Environment JList.
	 * @author BDS
	 */
	private class EnvironmentTransferHandler extends TransferHandler
	{
		private static final long serialVersionUID = 1L;

		/**
		 * Supports any drop location.
		 */
		public boolean canImport(TransferSupport transferSupport)
		{
			return true;
		}
		
		/**
		 * Capture the dragged Environment.
		 */
	    @SuppressWarnings("unchecked")
		protected Transferable createTransferable(JComponent component) 
	    {	        
	    	return ((JList<Environment>) component).getSelectedValue();
	    }
	    
	    /**
	     *  Supports only moving Environment objects.
	     */
	    public int getSourceActions(JComponent component) 
	    {
	        return TransferHandler.MOVE;
	    }
		
	    /**
	     * Moves the dragged Environment.
	     */
		@SuppressWarnings("unchecked")
		public boolean importData(TransferSupport transferSupport)
		{
			try
			{
				setEditedState(true);
				
				Environment dragEnvironment = (Environment) transferSupport.getTransferable().getTransferData(DataFlavor.stringFlavor);
				
				int previousIndex = ((JList<Environment>) transferSupport.getComponent()).getSelectedIndex();
				int newIndex = ((JList.DropLocation) transferSupport.getDropLocation()).getIndex();
				if (newIndex > previousIndex)
				{
					newIndex--;
				}
				
				((DefaultListModel<Environment>) ((JList<Environment>) transferSupport.getComponent()).getModel()).removeElement(dragEnvironment);
				((DefaultListModel<Environment>) ((JList<Environment>) transferSupport.getComponent()).getModel()).insertElementAt(dragEnvironment, newIndex);
				
				((JList<Environment>) transferSupport.getComponent()).setSelectedIndex(newIndex);
				((JList<Environment>) transferSupport.getComponent()).ensureIndexIsVisible(((JList<Environment>) transferSupport.getComponent()).getSelectedIndex());
				
				updateEnvironment();
			}
			catch (Exception exception)
			{
				Logger.logException(exception);
			}
			
			return true;
		}
	}

	/**
	 * FocusAdapter for the Environment JFormattedTextField objects.
	 * @author BDS
	 */
	private class EnvironmentFocusAdapter extends FocusAdapter
	{
		/**
		 * Updates the selected Environment.
		 */
		@Override
		public void focusLost(FocusEvent focusEvent)
		{
			updateEnvironment();
		}
	}

	/**
	 * KeyAdapter for the Environment JFormattedTextField objects.
	 * @author BDS
	 */
	private class EnvironmentKeyAdapter extends KeyAdapter
	{
		/**
		 * Clears focus when the Enter key is pressed, which will update the selected Environment.
		 */
		@Override
		public void keyTyped(KeyEvent keyEvent)
		{
			setEditedState(true);
			
			if (keyEvent.getKeyChar() == '\n')
			{
				resetFocus();
			}
		}
	}
}
